#!/usr/bin/python3
import os
from progressbar import ProgressBar
import requests
from requests.auth import HTTPBasicAuth
from lxml import etree
from base import http
from settings import ACCOUNT, PASSWORD, INSTALL_PACKAGE_PATH


class OpensuseObsAPI:
    """
    opensuse obs中的相关接口操作
    """

    host = "https://build.openeuler.org/"

    def __init__(self, project=None, account=None, password=None) -> None:
        self._project = project
        self._account = account or ACCOUNT
        self._password = password or PASSWORD
        self.file_size = 1024 * 20

    def _url(self, url, **kwargs):
        return self.host + url.format(**kwargs)

    def _meta(self, package, project):
        meta = """<package name="{package}" project="{project}">
                <title />
                <description />
              </package>""".format(
            package=package, project=project
        )
        return meta

    @property
    def _auth(self):
        return HTTPBasicAuth(self._account, self._password)

    def get_unresolvable(self, project_name=None):
        """
        获取某一个工程下的所有unresolvable的包信息
        """
        if not project_name:
            project_name = self._project
        url = self._url(url="build/{project}/_result", project=project_name)
        response = http.get(url, auth=self._auth)

        if response.status_code != 200:
            return None
        pkgs = dict()
        etree_element = etree.HTML(response.text)
        unresolvable_result = etree_element.xpath("//result")
        for _unresolvable in unresolvable_result:
            if _unresolvable.xpath("./@repository")[0] != "standard_aarch64":
                continue
            for status in _unresolvable.xpath("./status"):
                if status.xpath("./@code")[0] != "unresolvable":
                    continue
                package = status.xpath("./@package")[0]
                details = status.xpath("./details/text()")[0]
                pkgs[package] = []
                # 提取依赖关系
                for provides in details.split(","):
                    pkgs[package].append(
                        provides.replace("nothing provides", "").strip()
                    )
        return pkgs

    def upload_file(self, package, path, project=None):
        """
        上传文件
        """
        if not project:
            project = self._project
        if os.path.isfile(path):
            files = {path.split("/")[-1]: path}
        else:
            files = {
                file.split("/")[-1]: os.path.join(path, file)
                for file in os.listdir(path)
                if os.path.isfile(os.path.join(path, file))
            }
        progress_bar = ProgressBar().start()
        number = 0
        for file_name, file in files.items():
            url = self._url(
                url="source/{project}/{package}/{filename}",
                project=project,
                package=package,
                filename=file_name,
            )
            try:
                response = requests.put(
                    url,
                    auth=self._auth,
                    data=open(file, "rb"),
                )
                if response.status_code != 200:
                    raise requests.exceptions.HTTPError

            except Exception as error:
                print("上传软件包%s的文件失败:%s \n" % (package, file))
            number += 1
            progress_bar.update(int((number / len(files.keys())) * 100))
        print("\n")

    def create_package(self, package, project=None):
        """
        创建软件包
        """
        if not project:
            project = self._project
        url = self._url(
            url="source/{project}/{package}/_meta", project=project, package=package
        )
        meta = self._meta(package, project).encode("utf-8")
        response = requests.put(
            url,
            auth=self._auth,
            data=meta,
            timeout=15,
        )
        return response.status_code == 200

    def exists_package(self, package, project=None):
        """
        判断指定工程下是否存在特定的软件包
        """
        if not project:
            project = self._project
        url = self._url(
            url="source/{project}/{package}", project=project, package=package
        )
        response = http.get(url, auth=self._auth)
        if response.status_code == 404:
            return False
        if response.status_code == 200 and package in response.text:
            return True

    def build(self, project=None, package=None):
        """
        触发单个软件包或整个工程的编译
        """
        url = self._url(url="build/{project}?cmd=rebuild", project=project)
        data = None
        if package:
            data = {"package": package}
        response = requests.post(url, auth=self._auth, data=data)
        return response.status_code == 200

    def _project_meta(self, project):
        _meta = """<project name="{project}">
            <title />
            <description />
            <person userid="{account}" role="maintainer" />
            <debuginfo>
                <enable />
            </debuginfo>
            <repository name="standard_x86_64">
                <path project="openEuler:20.03:LTS:SP2:Epol" repository="standard_x86_64"/>
                <arch>x86_64</arch>
            </repository>
            <repository name="standard_aarch64">
                <path project="openEuler:20.03:LTS:SP2:Epol" repository="standard_aarch64"/>
                <arch>aarch64</arch>
            </repository>
            </project>
        """.format(
            project=project, account=self._account
        )
        return _meta

    def create_project(self, project):
        """
        创建工程
        """
        if not project:
            project = self._project
        url = self._url(url="source/{project}/_meta", project=project)
        meta = self._project_meta(project).encode("utf-8")
        response = requests.put(
            url,
            auth=self._auth,
            data=meta,
            timeout=15,
        )
        return response.status_code == 200

    def exists_project(self, project):
        """
        判断指定工程下是否存在特定的软件包
        """
        url = self._url(url="source/{project}", project=project)
        response = http.get(url, auth=self._auth)
        if response.status_code == 404:
            return False
        if response.status_code == 200:
            return True

    def get_source(self):
        """
        获取所有的工程
        """
        project = []
        response = http.get("http://117.78.1.88/user/show/gongzt", auth=self._auth)
        if response.status_code == 200:
            etree_element = etree.HTML(response.text)
            project = [
                project
                for project in etree_element.xpath(
                    "//*[@id='involved-projects-table']/tbody/tr/td/a/text()"
                )
            ]
        return project

    def _parse_build_result(self, xml):
        etree_element = etree.HTML(xml)
        _builds = dict()
        for build_result in etree_element.xpath("//result"):
            respository = build_result.xpath("./@arch")[0]
            _builds[respository] = dict()
            for status in build_result.xpath("//status"):
                code = status.xpath("./@code")[0]
                package = status.xpath("./@package")[0]
                try:
                    _builds[respository][code].append(package)
                except KeyError:
                    _builds[respository][code] = [package]
        return _builds

    def get_package_status(self, project, package=None, arch="aarch64"):
        """
        获取工程中的所有/特定软件包的状态
        """
        url = self._url(url="build/{project}/_result", project=project)
        response = http.get(url, auth=self._auth)
        if response.status_code != 200:
            return None
        build_result = self._parse_build_result(xml=response.text)
        _builds = dict()
        for _arch, result in build_result.items():
            if arch and _arch != arch:
                continue
            _builds[_arch] = dict()
            for status, pkgs in result.items():
                if not package:
                    _builds[_arch].update({pkg: status for pkg in pkgs})
                    continue
                if package in pkgs:
                    _builds[_arch][package] = status
                    break

            build_result = _builds
        return build_result

    def download_binary_file(
        self,
        project,
        package,
        binary_name,
        respository="standard_aarch64",
        arch="aarch64",
        save_path=None,
    ):
        """
        下载二进制rpm包
        """
        url = self._url(
            url="build/{project}/{repository}/{arch}/{package}/{binaryname}",
            project=project,
            repository=respository,
            arch=arch,
            package=package,
            binaryname=binary_name,
        )
        response = http.get(url, auth=self._auth)
        if response.status_code != 200:
            return False
        try:
            if save_path is None:
                save_path = os.path.join(INSTALL_PACKAGE_PATH, "check-install")
            if not os.path.exists(save_path):
                os.makedirs(save_path, exist_ok=True)
            with open(os.path.join(save_path, binary_name), "wb") as file:
                file.write(response.content)
            return True
        except IOError:
            return False

    def published_packages(
        self, project, package, repository="standard_aarch64", arch="aarch64"
    ):
        """
        发布的软件包
        """
        url = self._url(
            url="build/{project}/{repository}/{arch}/{package}",
            project=project,
            package=package,
            repository=repository,
            arch=arch,
        )
        response = http.get(url, auth=self._auth)
        if response.status_code != 200:
            return None
        etree_element = etree.HTML(response.text)
        try:
            filenames = [
                file
                for file in etree_element.xpath("//binary/@filename")
                if all(
                    [
                        trait not in file
                        for trait in ("debugsource", "debuginfo", "src.rpm", "debug")
                    ]
                )
                and file.endswith(".rpm")
            ]
            return filenames[-1]
        except (TypeError, IndexError):
            return None
